package cn.melonlib.cores.graphql.comp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.melonlib.cores.commons.comp.web.ctrl.BasicController;
import cn.melonlib.cores.model.anno.ModelIgnore;
import cn.melonlib.objects.maps.JsonMap;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.apache.commons.lang3.compare.ComparableUtils;
import org.hibernate.service.spi.ServiceException;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

@Component
public class GraphQLTools extends ApplicationObjectSupport {

    @Resource
    private OkHttpClient httpClient;

    @Resource
    private ObjectMapper jsonMapper;

    public <QUERY,RESULT>RESULT query(String url, GraphQLQueryBean<QUERY> bean,TypeReference<RESULT> resultTypeReference)throws Exception{
        StringBuilder queryStat=new StringBuilder("query").append(" ").append(bean.getName());
        if(bean.getVariables().size()>0){
            queryStat.append("(")
                    .append(bean.getVariables().stream().map(param->"$"+param.getName()+":"+param.getType()+(param.isRequired()?"!":"")).collect(Collectors.joining(",")))
                    .append(")");
        }
        queryStat.append("{\r\n").append(bean.getName());
        if(bean.getVariables().size()>0){
            queryStat.append("(")
                    .append(bean.getVariables().stream().map(param->param.getName()+":$"+param.getName()).collect(Collectors.joining(",")))
                    .append(")");
        }
        queryStat.append(this.createBody(bean));
        queryStat.append("}");
        logger.info(queryStat);
        JsonMap<Object> params= JsonMap.createMap().append("operationName",bean.getName())
                .append("query",queryStat.toString())
                .append("variables",bean.getVariables().stream().reduce(JsonMap.<Object>createMap(),(m,v)->m.append(v.getName(),v.getValue()),(m,v)->null));
        MediaType mediaType=MediaType.parse(BasicController.APPLICATION_JSON_UTF8_VALUE);
        RequestBody requestBody=RequestBody.create(params.jsonString(),mediaType);
        Request request=new Request.Builder().url(url).post(requestBody).build();
        try(Response response=httpClient.newCall(request).execute()){
            String body=response.body().string();
            logger.info(body);
            return jsonMapper.readValue(body, resultTypeReference);
        }
    }

    public <BODY>String createBody(BodyTypeReference<BODY> queryTypeReference){
        StringBuilder query=new StringBuilder("{\r\n");
        ResolvableType type=ResolvableType.forInstance(queryTypeReference).getSuperType();
//        System.out.println(type.getGenerics().length);
//        System.out.println(type.getGeneric(0).getGeneric(0).toClass());
        Class<?> entityType=type.getGeneric(0).getGeneric(0).toClass();
        query.append(Arrays.stream(ReflectUtils.getBeanGetters(type.getGeneric(0).toClass())).map(p->{
            if(ClassUtil.isBasicType(p.getPropertyType())||String.class.isAssignableFrom(p.getPropertyType())){
                return p.getName();
            }else if(Collection.class.isAssignableFrom(p.getPropertyType())){
                try {
                    Class<?> itemType=ResolvableType.forField(type.getGeneric(0).toClass().getDeclaredField(p.getName())).getGeneric(0).resolve();

                    if(Objects.isNull(itemType)){
                        return p.getName()+createBody(entityType,entityType,1);
                    }
                    return p.getName()+createBody(itemType,entityType,1);
                } catch (NoSuchFieldException e) {
                    throw new ServiceException(e.getMessage(),e);
                }
            }
            if(p.getPropertyType().equals(Object.class)){
                return p.getName()+createBody(entityType,entityType,1);
            }
            return p.getName()+createBody(p.getPropertyType(),entityType,1);
        }).collect(Collectors.joining("\r\n"))) ;
        query.append("\r\n}");
        return query.toString();
    }

    public String createBody(Class<?> queryType,Class<?> spType,int level){
        Class<?> type=defaultType(queryType,spType);
        StringBuilder query=new StringBuilder("{\r\n");
        query.append(Arrays.stream(ReflectUtils.getBeanGetters(type)).map(p->{
            if(ClassUtil.isBasicType(p.getPropertyType())||String.class.isAssignableFrom(p.getPropertyType())){
                return "\t".repeat(Math.max(0, level))+p.getName();
            }else if(Collection.class.isAssignableFrom(p.getPropertyType())){
                try {
                    Class<?> itemType=ResolvableType.forField(type.getDeclaredField(p.getName())).getGeneric(0).resolve();
                    return "\t".repeat(Math.max(0, level))+p.getName()+createBody(itemType,spType,level+1);
                } catch (NoSuchFieldException e) {
                    throw new ServiceException(e.getMessage(),e);
                }
            }
            return "\t".repeat(Math.max(0, level))+p.getName()+createBody(p.getPropertyType(),spType,level+1);
        }).collect(Collectors.joining("\r\n"))) ;
        query.append("\r\n").append("\t".repeat(Math.max(0, level-1))).append("}");
        return query.toString();
    }
    private Class<?> defaultType(Class<?> type,Class<?> defaultType){
        if(type!=null&&!Objects.equals(Object.class,type)){
            return type;
        }else{
            return defaultType;
        }
    }
    public <QUERY>String createQuery(QueryTypeReference<QUERY> queryTypeReference){
        StringBuilder query=new StringBuilder("{\r\n");
        ResolvableType type=ResolvableType.forInstance(queryTypeReference).getSuperType();
//        System.out.println(type.getGenerics().length);
//        System.out.println(type.getGeneric(0).getGeneric(0).toClass());
        Class<?> entityType=type.getGeneric(0).getGeneric(0).toClass();
        query.append(Arrays.stream(ReflectUtils.getBeanGetters(type.getGeneric(0).toClass())).map(p->{
            if(ClassUtil.isBasicType(p.getPropertyType())||String.class.isAssignableFrom(p.getPropertyType())){
                return p.getName();
            }else if(Collection.class.isAssignableFrom(p.getPropertyType())){
                try {
                    Class<?> itemType=ResolvableType.forField(type.getGeneric(0).toClass().getDeclaredField(p.getName())).getGeneric(0).resolve();
                    if(Objects.isNull(itemType)){
                        return p.getName()+createQuery(entityType,1);
                    }
                    if(ClassUtil.isBasicType(itemType)||String.class.isAssignableFrom(itemType)){
                        return p.getName();
                    }
                    return p.getName()+createQuery(itemType,1);
                } catch (NoSuchFieldException e) {
                    throw new ServiceException(e.getMessage(),e);
                }
            }
            if(p.getPropertyType().equals(Object.class)){
                return p.getName()+createQuery(entityType,1);
            }
            return p.getName()+createQuery(p.getPropertyType(),1);
        }).collect(Collectors.joining("\r\n"))) ;
        query.append("\r\n}");
        return query.toString();
    }

    public String createQuery(Method method){
        Class<?> returnType=method.getReturnType();
        if(ClassUtil.isBasicType(returnType)||String.class.isAssignableFrom(returnType)){
            return "";
        }else if(Collection.class.isAssignableFrom(returnType)){
            Class<?> itemType=ResolvableType.forMethodReturnType(method).getGeneric(0).resolve();
            return createQuery(itemType,0);
        }
        return createQuery(returnType,0);
    }

    public String createQuery(Class<?> queryType,int level){
        StringBuilder query=new StringBuilder("{\r\n");
        query.append(Arrays.stream(ReflectUtils.getBeanGetters(queryType)).filter(p->ReflectUtil.getField(queryType,p.getName())!=null).filter(
                p-> AnnotationUtils.findAnnotation(ReflectUtil.getField(queryType,p.getName()), ModelIgnore.class)==null).map(p->{
            if(ClassUtil.isBasicType(p.getPropertyType())||String.class.isAssignableFrom(p.getPropertyType())){
                return "\t".repeat(Math.max(0, level))+p.getName();
            }else if(Collection.class.isAssignableFrom(p.getPropertyType())){
                try {
                    Class<?> itemType=ResolvableType.forField(queryType.getDeclaredField(p.getName())).getGeneric(0).resolve();
                    if(ClassUtil.isBasicType(itemType)||String.class.isAssignableFrom(itemType)){
                        return "";
                    }
                    return "\t".repeat(Math.max(0, level))+p.getName()+createQuery(itemType,level+1);
                } catch (NoSuchFieldException e) {
                    throw new ServiceException(e.getMessage(),e);
                }
            }
            return "\t".repeat(Math.max(0, level))+p.getName()+createQuery(p.getPropertyType(),level+1);
        }).collect(Collectors.joining("\r\n"))) ;
        query.append("\r\n").append("\t".repeat(Math.max(0, level-1))).append("}");
        return query.toString();
    }

    public boolean filter(Object entity, JsonMap<? extends Object> filter){
        return filter.entrySet().stream().map(fentry->{
            Map<String,Object> conditions= (Map<String, Object>) filter.get(fentry.getKey());
            Object value=BeanUtil.getProperty(entity,fentry.getKey());
            return conditions.entrySet().stream().map(entry->{
                switch (entry.getKey()){
                    case "neq": return !Objects.equals(value,entry.getValue());
                    case "like":{
                        if(value instanceof String&&entry.getValue() instanceof String){
                            return value.toString().indexOf(((String) entry.getValue()))>=0;
                        }else{
                            return false;
                        }
                    }
                    case "in":{
                        if(entry.getValue() instanceof Collection){
                            return ((Collection<?>) entry.getValue()).contains(value);
                        }else{
                            return Objects.equals(value,entry.getValue());
                        }
                    }
                    case "nin":{
                        if(entry.getValue() instanceof Collection){
                            return !((Collection<?>) entry.getValue()).contains(value);
                        }else{
                            return !Objects.equals(value,entry.getValue());
                        }
                    }
                    case "gt":{
                        if(value == null ){
                            return false;
                        }
                        if(value instanceof Comparable&&entry.getValue() instanceof Comparable) {
                            return ComparableUtils.gt((Comparable) entry.getValue()).test(value);
                        }else{
                            return ComparableUtils.gt(entry.getValue().toString()).test(value.toString());
                        }
                    }
                    case "gte":{
                        if(value == null ){
                            return false;
                        }
                        if(value instanceof Comparable&&entry.getValue() instanceof Comparable) {
                            return ComparableUtils.ge((Comparable)entry.getValue()).test( value);
                        }else{
                            return ComparableUtils.ge(entry.getValue().toString()).test(value.toString());
                        }
                    }
                    case "lt":{
                        if(value == null ){
                            return false;
                        }
                        if(value instanceof Comparable&&entry.getValue() instanceof Comparable) {
                            return ComparableUtils.lt((Comparable)entry.getValue()).test( value);
                        }else{
                            return ComparableUtils.lt(entry.getValue().toString()).test(value.toString());
                        }
                    }
                    case "le":{
                        if(value == null ){
                            return false;
                        }
                        if(value instanceof Comparable&&entry.getValue() instanceof Comparable) {
                            return ComparableUtils.le((Comparable)entry.getValue()).test( value);
                        }else{
                            return ComparableUtils.le(entry.getValue().toString()).test(value.toString());
                        }
                    }
                    case "eq":
                    default: return Objects.equals(value,entry.getValue());
                }
            }).reduce(true,(root,item)->{
                return root&&item;
            });
        }).reduce(true,(root,item)->root&&item);

    }

    public static class QueryTypeReference<QUERY>{

    }
}
